Heap & Stack in Java
contents
스택(Stack)과 힙(Heap)의 상호작용 은 자바 실행 모델에서 가장 중요한 개념 중 하나입니다. 이 원리를 이해하면, 메서드 내부에서 객체의 내용을 바꿀 때(modify)는 원본이 변하지만, 변수에 다른 값을 재할당(reassign)할 때는 원본이 변하지 않는 이유를 명확히 알 수 있습니다.
핵심은 참조(Stack에 있는 주소) 와 실제 데이터(Heap에 있는 객체) 를 구분하는 것입니다.
골든 룰 (The Golden Rule)
자바는 언제나 '값에 의한 전달(Pass-by-Value)'입니다.
메서드에 객체를 넘길 때, 객체 그 자체를 넘기는 것이 아닙니다. 그 객체를 가리키는 메모리 주소(참조)의 복사본을 넘기는 것입니다.
시나리오
main 메서드가 modifyBuilder라는 메서드를 호출하는 상황을 추적해 보겠습니다.
public class MemoryTest {
public static void main(String[] args) {
// 라인 1
StringBuilder sb = new StringBuilder("Hi");
// 라인 2
modify(sb);
// 라인 3: 여기서 sb는 무엇일까요?
System.out.println(sb);
}
public static void modify(StringBuilder x) {
// 라인 4
x.append(" Java");
// 라인 5
x = null;
}
}
단계별 메모리 상황은 다음과 같습니다.
1단계: main 시작 (라인 1)
- 스택(Stack): JVM이
main메서드를 위한 스택 프레임을 생성합니다. - 실행:
StringBuilder sb = new StringBuilder("Hi");- 힙(Heap): JVM이 힙 영역에
StringBuilder객체를 위한 메모리를 할당합니다. 이 주소를 @500이라고 가정합시다. 여기엔 "Hi"라는 값이 들어갑니다. - 스택(Stack):
main스택 프레임 안에 변수sb가 생성됩니다.sb는 객체 자체가 아니라, 주소인 @500을 저장합니다.
- 힙(Heap): JVM이 힙 영역에
| 스택 (main 프레임) | 힙 (Heap) |
|---|---|
sb -> @500 |
주소 @500: StringBuilder 객체 { value: "Hi" } |
2단계: 메서드 호출 (라인 2)
- 실행:
modify(sb); - 스택: JVM이
main위에modify메서드를 위한 새로운 스택 프레임을 올립니다(Push). - 값에 의한 전달: 자바는
sb안에 들어있는 값(즉, 주소 @500)을 복사해서 메서드 파라미터x에 넣어줍니다.
이제 서로 다른 두 스택 프레임에 있는 두 변수가 같은 객체를 가리키게 됩니다.
| 스택 (modify 프레임) | 스택 (main 프레임) | 힙 (Heap) |
|---|---|---|
x -> @500 |
sb -> @500 |
주소 @500: { value: "Hi" } |
3단계: 객체 수정 (라인 4)
- 실행:
x.append(" Java"); - 동작:
- JVM은 현재 스택 프레임의 변수
x를 봅니다. - 주소 @500을 찾습니다.
- 그 주소를 따라 힙(Heap)에 있는 @500으로 이동합니다.
- 거기 있는 실제 객체 데이터를 수정합니다.
- JVM은 현재 스택 프레임의 변수
힙에 있는 객체가 변했습니다. main에 있는 sb도 여전히 @500을 가리키고 있으므로, sb 입장에서도 내용이 바뀐 것으로 보입니다.
| 스택 (modify 프레임) | 스택 (main 프레임) | 힙 (Heap) |
|---|---|---|
x -> @500 |
sb -> @500 |
주소 @500: { value: "Hi Java" } |
4단계: "재할당"의 함정 (라인 5)
- 실행:
x = null;(혹은x = new StringBuilder("New");등) - 동작:
- 이 작업은 현재 스택 프레임에 있는 변수
x의 내용만 바꿉니다. - 이제
x는null을 가집니다. - 중요: 이것은 힙에 있는 객체(@500)를 건드리지 않습니다. 또한
main프레임에 있는 변수sb도 건드리지 않습니다.
- 이 작업은 현재 스택 프레임에 있는 변수
| 스택 (modify 프레임) | 스택 (main 프레임) | 힙 (Heap) |
|---|---|---|
x -> null |
sb -> @500 |
주소 @500: { value: "Hi Java" } |
5단계: 메서드 반환 (라인 3)
- 동작:
modify메서드가 끝납니다. - 스택:
modify스택 프레임이 제거(Pop) 됩니다. 변수x는 영원히 사라집니다. - 복귀: 제어권이
main으로 돌아옵니다. - 상태:
sb는 여전히 @500을 가리키고 있습니다. - 결과:
sb를 출력하면"Hi Java"가 나옵니다.
상호작용 요약
- 생성(Creation):
new키워드는 힙에 메모리를 할당하고 주소를 반환합니다. - 저장(Storage): 스택의 변수는 그 주소(참조)를 저장합니다.
- 전달(Passing): 메서드를 호출할 때, 스택은 그 주소를 복사해서 새 프레임에 전달합니다.
- 접근(Access): 점 연산자(
.)는 JVM에게 스택의 참조를 따라 힙의 객체로 가서 데이터를 읽거나 쓰라고 명령하는 것입니다. - 범위(Scope): 스택 프레임이 사라지면 그 안의 참조변수도 사라집니다. 힙에 있는 객체는 어떤 스택 변수도 자기를 가리키지 않을 때 가비지 컬렉터(GC) 에 의해 사라집니다.
references